﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Security.AccessControl;
using System.Runtime.Serialization;
using SharpGameLibrary;

using GDIBitmap = System.Drawing.Bitmap;
using GDIColor = System.Drawing.Color;
using GDIImage = System.Drawing.Image;
using GDIGraphics = System.Drawing.Graphics;
using GDIGraphicsUnit = System.Drawing.GraphicsUnit;
using GDIRectangle = System.Drawing.Rectangle;

namespace Editor
{
    public partial class Form1 : Form
    {
        #region Fields

        bool isMapItemsEnabled;

        Bitmap tilesetCanvas;
        Bitmap tilesetLayer1;

        Point clickImagePanel1;
        bool isDownImagePanel1;
        bool isOverImagePanel1;

        Point curTile;
        bool isDownImagePanel2;

        Rectangle rectTiles;
        Object[] selectedTiles;

        Bitmap mapDisplayCanvas;
        List<Bitmap> mapDisplayLayers;
        Bitmap mapDisplayAttributes;
        Bitmap mapDisplayEntities;

        #endregion

        #region Tileset

        #region Methods

        /// <summary>
        /// Load the tilesets.
        /// </summary>
        /// <remarks>Must be called in initialisation.</remarks>
        private void fetchTilesets()
        {
            Directory.CreateDirectory("GFX/Tilesets");

            string[] tilesetsPaths = Directory.GetFiles(@"GFX/Tilesets", "*.png");
            string[] tilesetsNames = new string[tilesetsPaths.Length + 1];

            //this.tilesets = new Tileset[tilesetsPaths.Length + 1];

            if (tilesetsPaths.Length > 0)
            {
                // // TODO: Stop loading all the images like that. It consumes too much memory.
                for (int i = 0; i < tilesetsPaths.Length; i++)
                {
                    tilesetsNames[i] = tilesetsPaths[i].Split('\\')[1].Split('.')[0];

                    //this.tilesets[i] = new Tileset(new Bitmap(tilesetsPaths[i]), tilesetsNames[i]);
                    comboBox1.Items.Add(tilesetsNames[i]);

                    WorldMap.addTileset(tilesetsNames[i], new Tileset(new Bitmap(tilesetsPaths[i]), tilesetsNames[i]));
                }             
            }
            else
            {
                MessageBox.Show("Aucun tileset n'a été trouvé dans votre répertoire " +
                    Directory.GetCurrentDirectory() + "\\GFX\\Tilesets.", "Attention",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning,
                    MessageBoxDefaultButton.Button1);

                tilesetsNames[0] = "Aucun tileset trouvé";
            }
        }

        private void paintTilesetRect(Rectangle rect)
        {
            Bitmap tilesetRect = new Bitmap(rect.Width + 2, rect.Height + 2);

            Graphics g = Methods.toGraphics(tilesetRect);
            g.Clear(GDIColor.White);
            g.DrawRectangle(Pens.Red, new Rectangle(0, 0, rect.Width, rect.Height));
            g.Dispose();

            try { this.tilesetCanvas.Dispose(); }
            catch (Exception) { }

            this.tilesetCanvas = (Bitmap)this.tilesetLayer1.Clone();
            Graphics gr = Methods.toGraphics(tilesetCanvas);
            gr.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
            tilesetRect.MakeTransparent();
            gr.DrawImage(tilesetRect, rect.X, rect.Y);
            gr.Dispose();

            this.fillTilesetReview(tilesetCanvas);
        }

        private void fillTilesetReview()
        {
            imagePanel1.Image = WorldMap.getTileset(comboBox1.SelectedItem.ToString()).Image;
        }

        private void fillTilesetReview(Bitmap image)
        {
            imagePanel1.Image = image;
            imagePanel1.Update();
        }

        private void initializeTilesetLayers()
        {
            this.tilesetLayer1 = new Bitmap(WorldMap.getTileset(comboBox1.SelectedItem.ToString()).Image);
        }

        #endregion

        #region Events

        private void imagePanel1_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.isDownImagePanel1)
            {
                Point curPos = new Point((int)(((int)(imagePanel1.HScrollBarValue / imagePanel1.Zoom) + e.X) /
                   TileEngine.Tile.Width) * TileEngine.Tile.Width,
                   (int)(((int)(imagePanel1.VScrollBarValue / imagePanel1.Zoom) + e.Y) /
                    TileEngine.Tile.Height) * TileEngine.Tile.Height);

                if (!curPos.Equals(this.clickImagePanel1))
                {
                    Point pt = new Point(this.clickImagePanel1.X, this.clickImagePanel1.Y);
                    Size selectedTiles = new Size(TileEngine.Tile.Width, TileEngine.Tile.Height);

                    if (curPos.X > this.clickImagePanel1.X)
                    {
                        pt.X = (int)(((int)(imagePanel1.HScrollBarValue / imagePanel1.Zoom) + e.X) /
                        TileEngine.Tile.Width) * TileEngine.Tile.Width + TileEngine.Tile.Width;

                        selectedTiles.Width += curPos.X - this.clickImagePanel1.X;
                    }
                    else if (curPos.X < this.clickImagePanel1.X)
                    {
                        pt.X = (int)(((int)(imagePanel1.HScrollBarValue / imagePanel1.Zoom) + e.X) /
                        TileEngine.Tile.Width) * TileEngine.Tile.Width;

                        selectedTiles.Width += this.clickImagePanel1.X - curPos.X;
                    }

                    if (curPos.Y > this.clickImagePanel1.Y)
                    {
                        pt.Y = (int)(((int)(imagePanel1.VScrollBarValue / imagePanel1.Zoom) + e.Y) /
                        TileEngine.Tile.Height) * TileEngine.Tile.Height + TileEngine.Tile.Height;

                        selectedTiles.Height += curPos.Y - this.clickImagePanel1.Y;
                    }
                    else if (curPos.Y < this.clickImagePanel1.Y)
                    {
                        pt.Y = (int)(((int)(imagePanel1.VScrollBarValue / imagePanel1.Zoom) + e.Y) /
                        TileEngine.Tile.Height) * TileEngine.Tile.Height;

                        selectedTiles.Height += this.clickImagePanel1.Y - curPos.Y;
                    }

                    this.rectTiles = new Rectangle();
                    this.rectTiles.Size = selectedTiles;

                    if (curPos.X >= this.clickImagePanel1.X && curPos.Y >= this.clickImagePanel1.Y)
                        this.rectTiles.Location = this.clickImagePanel1;
                    else if (curPos.X <= this.clickImagePanel1.X && curPos.Y <= this.clickImagePanel1.Y)
                        this.rectTiles.Location = pt;
                    else if (curPos.X > this.clickImagePanel1.X && curPos.Y < this.clickImagePanel1.Y)
                    {
                        this.rectTiles.X = this.clickImagePanel1.X;
                        this.rectTiles.Y = pt.Y;
                    }
                    else if (curPos.X < this.clickImagePanel1.X && curPos.Y > this.clickImagePanel1.Y)
                    {
                        this.rectTiles.X = pt.X;
                        this.rectTiles.Y = this.clickImagePanel1.Y;
                    }
                }
                else
                    this.rectTiles = new Rectangle(this.clickImagePanel1, TileEngine.Tile);

                this.paintTilesetRect(this.rectTiles);
            }
        }

        private void imagePanel1_MouseDown(object sender, MouseEventArgs e)
        {
            if (imagePanel1.Image != null)
            {
                this.isDownImagePanel1 = true;

                this.clickImagePanel1 = new Point((int)(((int)(imagePanel1.HScrollBarValue / imagePanel1.Zoom) + e.X) / 
                    TileEngine.Tile.Width) * TileEngine.Tile.Width,
                    (int)(((int)(imagePanel1.VScrollBarValue / imagePanel1.Zoom) + e.Y) /
                    TileEngine.Tile.Height) * TileEngine.Tile.Height);
                
                this.rectTiles = new Rectangle(this.clickImagePanel1, TileEngine.Tile);

                this.paintTilesetRect(this.rectTiles);
            }
        }

        private void imagePanel1_MouseUp(object sender, MouseEventArgs e)
        {
            this.isDownImagePanel1 = false;

            int nbSelectedTiles = (this.rectTiles.Width / TileEngine.Tile.Width) *
                (this.rectTiles.Height / TileEngine.Tile.Height);

            this.selectedTiles = new Object[nbSelectedTiles];

            int index = 0;

            for (int y = this.rectTiles.Y; y < this.rectTiles.Y + this.rectTiles.Height; y += TileEngine.Tile.Height)
                for (int x = this.rectTiles.X; x < this.rectTiles.X + this.rectTiles.Width; x += TileEngine.Tile.Width)
                {
                    this.selectedTiles[index] = new Tuple<int, int>(this.rectTiles.X / TileEngine.Tile.Width,
                        this.rectTiles.Y / TileEngine.Tile.Height);
                    
                    index++;
                }
        }

        private void imagePanel1_MouseEnter(object sender, EventArgs e)
        {
            this.imagePanel1.Select();
            this.isOverImagePanel1 = true;
        }

        private void imagePanel1_MouseLeave(object sender, EventArgs e)
        {
            this.isOverImagePanel1 = false;
        }

        private void imagePanel1_MouseWheel(object sender, MouseEventArgs e)
        {
            if (this.isOverImagePanel1)
                this.imagePanel1.VScrollBarValue -= e.Delta;
        }

        #endregion

        #endregion

        #region Map Display

        #region Methods

        private void paintMapDisplayRect(Rectangle rect)
        {
            Bitmap mapDisplayRect = new Bitmap(rect.Width + 2, rect.Height + 2);

            Graphics g = Methods.toGraphics(mapDisplayRect);
            g.Clear(GDIColor.White);
            g.DrawRectangle(Pens.Red, new Rectangle(0, 0, rect.Width, rect.Height));
            g.Dispose();

            try { this.mapDisplayCanvas.Dispose(); }
            catch (Exception) { }

            this.mapDisplayCanvas = new Bitmap(Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height);

            mapDisplayRect.MakeTransparent();

            Graphics gr = Methods.toGraphics(this.mapDisplayCanvas);
            gr.Clear(Color.CornflowerBlue);

            this.paintMapDisplayLayers(gr);
            this.paintDisplayAttributes(gr);
            this.paintDisplayEntities(gr);

            gr.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
            gr.DrawImage(mapDisplayRect, rect.X, rect.Y);
            gr.Dispose();

            this.fillMapDisplayReview(this.mapDisplayCanvas);
        }

        private void paintMapDisplayTile(Rectangle rect, Tile tile)
        {
            Bitmap mapDisplayRect = new Bitmap(tile.Image);

            Graphics g = Methods.toGraphics(this.mapDisplayCanvas);
            g.Clear(Color.CornflowerBlue);
            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
            g.DrawImage(mapDisplayRect, rect.X * TileEngine.Tile.Width, rect.Y * TileEngine.Tile.Height);
            g.Dispose();

            this.fillMapDisplayReview(this.mapDisplayCanvas);
        }

        private void paintMapDisplayLayers(Graphics g)
        {
            Rectangle rect = new Rectangle(0, 0, Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height);

            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;

            foreach (Bitmap elem in this.mapDisplayLayers)
                g.DrawImage(elem, rect);
        }

        private void paintDisplayAttributes(Graphics g)
        {
            if (!checkBox1.Checked)
                return;

            Rectangle rect = new Rectangle(0, 0, Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height);

            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;

            g.DrawImage(this.mapDisplayAttributes, rect);
        }

        private void paintDisplayEntities(Graphics g)
        {
            if (!checkBox3.Checked)
                return;

            Rectangle rect = new Rectangle(0, 0, Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height);

            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;

            g.DrawImage(this.mapDisplayEntities, rect);
        }

        private Graphics paintTransparence(Rectangle rect, Bitmap bmp)
        {
            //Create an invisible bitmap
            Bitmap toClear = new Bitmap(rect.Width, rect.Height);

            Graphics g = Methods.toGraphics(toClear);
            g.FillRectangle(Brushes.Black, 0, 0, toClear.Width, toClear.Height);
            toClear.MakeTransparent();

            g = Methods.toGraphics(bmp);

            // Clear what was drawn in the old layer
            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
            g.DrawImage(toClear, rect);

            return g;
        }

        private void paintMapDisplay()
        {
            Graphics g = Methods.toGraphics(this.mapDisplayCanvas);
            g.Clear(Color.CornflowerBlue);

            this.paintMapDisplayLayers(g);
            this.paintDisplayAttributes(g);
            this.paintDisplayEntities(g);

            g.Dispose();

            this.fillMapDisplayReview(this.mapDisplayCanvas);
        }

        private void fillMapDisplayReview()
        {
            imagePanel2.Image = this.mapDisplayCanvas;
        }

        private void fillMapDisplayReview(Bitmap image)
        {
            imagePanel2.Image = image;
            imagePanel2.Update();
        }

        private void drawAttribute(string attribute)
        {
            this.drawAttribute(attribute, this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height);
        }

        private void drawAttribute(string attribute, int tileX, int tileY)
        {
            StringFormat strFormat = new StringFormat();
            strFormat.Alignment = StringAlignment.Far;
            strFormat.LineAlignment = StringAlignment.Far;

            Graphics g = Methods.toGraphics(this.mapDisplayAttributes);

            g.DrawString(attribute, new Font("Quartz MS", 13), Brushes.Red, new Rectangle(tileX * TileEngine.Tile.Width, tileY * TileEngine.Tile.Height,
                TileEngine.Tile.Width, TileEngine.Tile.Height), strFormat);

            strFormat.Dispose();
            g.Dispose();
        }

        private void drawEntity(string id)
        {
            this.drawEntity(id, this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height);
        }

        private void drawEntity(string id, int tileX, int tileY)
        {
            StringFormat strFormat = new StringFormat();
            strFormat.Alignment = StringAlignment.Near;
            strFormat.LineAlignment = StringAlignment.Near;

            Graphics g = Methods.toGraphics(this.mapDisplayAttributes);

            g.DrawString(id, new Font("Quartz MS", 13), Brushes.Blue, new Rectangle(tileX * TileEngine.Tile.Width, tileY * TileEngine.Tile.Height,
                TileEngine.Tile.Width, TileEngine.Tile.Height), strFormat);

            strFormat.Dispose();
            g.Dispose();
        }

        private void drawMapDisplayTiles()
        {
            if (!WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).haveTileset(WorldMap.getTileset(comboBox1.SelectedItem.ToString()).Name))
                WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).addTileset(WorldMap.getTileset(comboBox1.SelectedItem.ToString()).Name);

            Bitmap tilesImg = new Bitmap(this.rectTiles.Width, this.rectTiles.Height);
            Graphics g = Methods.toGraphics(tilesImg);
            Rectangle dest;
            Rectangle source;

            int offsetY = 0;

            for (int y = this.rectTiles.Y; y < this.rectTiles.Y + this.rectTiles.Height; y += TileEngine.Tile.Height)
            {
                int offsetX = 0;

                for (int x = this.rectTiles.X; x < this.rectTiles.X + this.rectTiles.Width; x += TileEngine.Tile.Width)
                {
                    Point point = new Point((int)(this.curTile.X / TileEngine.Tile.Width) + offsetX,
                        (int)(this.curTile.Y / TileEngine.Tile.Height) + offsetY);
                    
                    //Ensures that it will not draw/add tiles out of the map.
                    if (point.X >= Map.WidthInTile || point.Y >= Map.HeightInTile)
                        break;

                    Size size = new Size(TileEngine.Tile.Width, TileEngine.Tile.Height);

                    dest = new Rectangle(point, size);

                    //add the tile
                    WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers[comboBox2.SelectedIndex].setTile(WorldMap.getTileset(comboBox1.SelectedItem.ToString()).Name,
                        (int)(x / TileEngine.Tile.Width), (int)(y / TileEngine.Tile.Height), dest.X, dest.Y);

                    source = new Rectangle(x, y, TileEngine.Tile.Width, TileEngine.Tile.Height);

                    g.DrawImage(WorldMap.getTileset(comboBox1.SelectedItem.ToString()).Image, offsetX * TileEngine.Tile.Width,
                        offsetY * TileEngine.Tile.Height, source, GDIGraphicsUnit.Pixel);

                    offsetX++;
                }

                offsetY++;
            }

            g.Dispose();

            this.drawTiles(tilesImg, this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height, comboBox2.SelectedIndex);
        }

        private void drawTiles(Bitmap tilesImg, int posX, int posY, int layer)
        {
            Point pt = new Point(posX * TileEngine.Tile.Height, posY * TileEngine.Tile.Width);
            Rectangle rect = new Rectangle(pt, new Size(tilesImg.Width, tilesImg.Height));

            Graphics g = this.paintTransparence(rect, this.mapDisplayLayers[layer]);

            // Paint new stuff
            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
            g.DrawImage(tilesImg, pt);

            g.Dispose();
        }

        private void eraseTile(Bitmap bmp)
        {
            Rectangle rect = new Rectangle(this.curTile, TileEngine.Tile);

            Graphics g = this.paintTransparence(rect, bmp);
            g.Dispose();
        }

        private bool isAnAttributeChecked()
        {
            return radioButton1.Checked || radioButton3.Checked;
        }

        private string setAttribute()
        {
            string charToDisplay = "";

            if (radioButton3.Checked)   //Block
            {
                charToDisplay = "B";
                WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Attributes[this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height].IsBlocked = true;
            }
            else if (radioButton1.Checked)  //Death
            {
                charToDisplay = "M";
                WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Attributes[this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height].IsDeath = true;
            }

            return charToDisplay;
        }

        private void fillTile()
        {
            for (int y = 0; y < Map.HeightInTile * TileEngine.Tile.Height; y += TileEngine.Tile.Height)
                for (int x = 0; x < Map.WidthInTile * TileEngine.Tile.Width; x += TileEngine.Tile.Width)
                {
                    this.curTile = new Point(x, y);
                    this.drawMapDisplayTiles();
                }
        }

        private void fillAttribute()
        {
            for (int y = 0; y < Map.HeightInTile * TileEngine.Tile.Height; y += TileEngine.Tile.Height)
                for (int x = 0; x < Map.WidthInTile * TileEngine.Tile.Width; x += TileEngine.Tile.Width)
                {
                    this.curTile = new Point(x, y);
                    this.drawAttribute(this.setAttribute());
                }
        }

        /// <summary>
        /// Initialize the bmps need for the display.
        /// </summary>
        /// <param name="toLoad">Specify if the map is new.</param>
        /// <seealso cref="initializeBmpsLayers"/>
        private void initializeBmps(bool isNew = true)
        {
            this.mapDisplayCanvas = new Bitmap(Map.WidthInTile * TileEngine.Tile.Width,
                Map.HeightInTile * TileEngine.Tile.Height);

            Graphics g = Methods.toGraphics(this.mapDisplayCanvas);
            g.Clear(Color.CornflowerBlue);
            g.Dispose();

            this.fillMapDisplayReview();

            this.mapDisplayLayers = new List<Bitmap>();

            if (isNew)
                this.mapDisplayLayers.Add(new Bitmap(Map.WidthInTile * TileEngine.Tile.Width,
                    Map.HeightInTile * TileEngine.Tile.Height));

            this.mapDisplayAttributes = new Bitmap(Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height);
            this.mapDisplayEntities = new Bitmap(Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height);
        }

        /// <summary>
        /// Add the bmps for each layer.
        /// </summary>
        /// <remarks>Call this when loading an existing map.</remarks>
        private void initializeBmpsLayers()
        {
            foreach (Layer layer in WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers)
                this.mapDisplayLayers.Add(new Bitmap(Map.WidthInTile * TileEngine.Tile.Width, Map.HeightInTile * TileEngine.Tile.Height));
        }

        private void createMap(bool isNew = true)
        {
            this.createMap(this.getIdByIndex(listBox1.Items.Count - 1) + 1, isNew);
        }

        private void createMap(int id, bool isNew = true)
        {
            if (!isMapItemsEnabled)
                this.enableMapItems();

            WorldMap.addMap(new Map(id));

            this.selectMap(listBox1.Items.Count);

            this.initializeBmps(isNew);

            if (isNew)
                this.createLayer();
        }

        private int getIdByIndex(int index)
        {
            Map map = (Map)listBox1.Items[index];

            return map.Index;
        }

        private void createLayer()
        {
            int id = this.getIdByIndex(listBox1.SelectedIndex);
            Map map = WorldMap.getMap(id);

            map.createLayer((map.Layers.Count).ToString());

            this.updateComboBox2();
        }

        private void restoreMap()
        {
            this.updateComboBox2();

            this.initializeBmpsLayers();
            this.restoreLayers();
            this.restoreAttributes();
            this.restoreEntities();

            this.paintMapDisplay();
        }

        private void restoreAttributes()
        {
            for (int y = 0; y < Map.HeightInTile; y++)
                for (int x = 0; x < Map.WidthInTile; x++)
                {
                    if (WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Attributes[x, y].IsBlocked)
                        this.drawAttribute("B", x, y);
                    else if (WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Attributes[x, y].IsDeath)
                        this.drawAttribute("M", x, y);
                }
        }

        private void restoreEntities()
        {
            int entityId;

            for (int y = 0; y < Map.HeightInTile; y++)
                for (int x = 0; x < Map.WidthInTile; x++)
                {
                    entityId = WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Entities[x, y].Id;
                    if (entityId != -1)
                        this.drawEntity(entityId.ToString(), x, y);
                }
        }

        private void restoreLayers()
        {
            Bitmap tilesImg;
            Graphics g;
            Rectangle dest;
            Rectangle source;

            for (int i = 0; i < WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers.Count; i++)
            {
                for (int y = 0; y != Map.HeightInTile; y++)
                    for (int x = 0; x != Map.WidthInTile; x++)
                    {
                        int tilePosXInTileset = WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers[i].getTilePosXInTileset(x, y);

                        if (tilePosXInTileset == -1)
                            continue;

                        int tilePosYInTileset = WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers[i].getTilePosYInTileset(x, y);

                        Point point = new Point(0, 0);
                        Size size = new Size(TileEngine.Tile.Width, TileEngine.Tile.Height);

                        dest = new Rectangle(point, size);
                        source = new Rectangle(tilePosXInTileset * TileEngine.Tile.Width, tilePosYInTileset * TileEngine.Tile.Height,
                            TileEngine.Tile.Width, TileEngine.Tile.Height);

                        tilesImg = new Bitmap(TileEngine.Tile.Width, TileEngine.Tile.Height);
                        g = Methods.toGraphics(tilesImg);

                        g.DrawImage(WorldMap.getTileset(WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers[i].getTilesetName(x, y)).Image, 
                            0, 0, source, GDIGraphicsUnit.Pixel);

                        this.drawTiles(tilesImg, x, y, i);

                        g.Dispose();
                        tilesImg.Dispose();
                    }
            }
        }

        private void addElem()
        {
            if (tabControl1.SelectedIndex == 0 && this.selectedTiles != null)
                this.drawMapDisplayTiles();
            else if (tabControl1.SelectedIndex == 1)
            {
                string charToDisplay = this.setAttribute();
                this.drawAttribute(charToDisplay);
            }
            else if (tabControl1.SelectedIndex == 2)
            {
                WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Entities[this.curTile.X / TileEngine.Tile.Width,
                    this.curTile.Y / TileEngine.Tile.Height].Id = Convert.ToInt32(textBox1.Text);
                this.drawEntity(textBox1.Text);
            }
        }

        private void deleteElem()
        {
            switch (tabControl1.SelectedIndex)
            {
                case 0:
                    WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers[comboBox2.SelectedIndex].removeTile(this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height);
                    this.eraseTile(this.mapDisplayLayers[comboBox2.SelectedIndex]);
                    break;
                case 1:
                    WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Attributes[this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height].clean();
                    this.eraseTile(this.mapDisplayAttributes);
                    break;
                case 2:
                    WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Entities[this.curTile.X / TileEngine.Tile.Width, this.curTile.Y / TileEngine.Tile.Height].Id = -1;
                    this.eraseTile(this.mapDisplayEntities);
                    break;
            }
        }

        private void mapDisplayAction()
        {
            if (!checkBox2.Checked)
                this.addElem();
            else
                this.deleteElem();
        }

        #endregion

        #region Events

        private void imagePanel2_MouseDown(object sender, MouseEventArgs e)
        {
            this.isDownImagePanel2 = true;

            this.curTile = new Point((int)(((int)(imagePanel2.HScrollBarValue / imagePanel2.Zoom) + e.X) /
                TileEngine.Tile.Width) * TileEngine.Tile.Width,
                (int)(((int)(imagePanel2.VScrollBarValue / imagePanel2.Zoom) + e.Y) /
                TileEngine.Tile.Height) * TileEngine.Tile.Height);

            this.mapDisplayAction();

            Size selectedTiles = new Size(this.rectTiles.Width, this.rectTiles.Height);
            Rectangle reviewRect = new Rectangle(this.curTile, selectedTiles);

            this.paintMapDisplayRect(reviewRect);
        }

        private void imagePanel2_MouseMove(object sender, MouseEventArgs e)
        {
            Point curTile = new Point((int)(((int)(imagePanel2.HScrollBarValue / imagePanel2.Zoom) + e.X) /
                TileEngine.Tile.Width) * TileEngine.Tile.Width,
                (int)(((int)(imagePanel2.VScrollBarValue / imagePanel2.Zoom) + e.Y) /
                TileEngine.Tile.Height) * TileEngine.Tile.Height);

            if (!(this.curTile.Equals(curTile)))
            {
                this.curTile = curTile;

                if (this.isDownImagePanel2)
                    this.mapDisplayAction();

                Size selectedTiles = new Size(this.rectTiles.Width, this.rectTiles.Height);
                Rectangle reviewRect = new Rectangle(curTile, selectedTiles);

                this.paintMapDisplayRect(reviewRect);
            }              
        }

        private void imagePanel2_MouseUp(object sender, MouseEventArgs e)
        {
            this.isDownImagePanel2 = false;
        }

        #endregion

        #endregion

        #region Enable

        private void enableGameItems()
        {
            enregistrerToolStripMenuItem.Enabled = true;
            carteToolStripMenuItem.Enabled = true;
            propriétésToolStripMenuItem.Enabled = true;
        }

        private void enableMapItems()
        {
            saveToolStripMenuItem.Enabled = true;
            propriétésToolStripMenuItem1.Enabled = true;

            tabControl1.Enabled = true;
            tabPage1.BackColor = System.Drawing.Color.White;

            comboBox1.Enabled = true;

            imagePanel1.Enabled = true;
            imagePanel1.BackColor = System.Drawing.Color.Black;

            listBox1.Enabled = true;
            listBox1.BackColor = System.Drawing.SystemColors.Window;

            imagePanel2.Enabled = true;
            imagePanel1.BackColor = System.Drawing.Color.Black;

            layerToolStripMenuItem.Enabled = true;
            comboBox2.Enabled = true;

            checkBox2.Enabled = true;
            button1.Enabled = true;

            this.isMapItemsEnabled = true;
        }

        #endregion

        #region Constructor

        public Form1()
        {
            InitializeComponent();

            this.isMapItemsEnabled = false;
            this.isOverImagePanel1 = false;
            this.isDownImagePanel1 = false;
        }

        #endregion

        #region ToolStripMenu

        #region Game

        /// <summary>
        /// Initialise what is need to create a new game.
        /// </summary>
        private void nouveauToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.fetchTilesets();
            comboBox1.SelectedIndex = 0;

            this.enableGameItems();
        }

        private void saveAllMaps(string dir)
        {
            this.initMapSave(dir);
            for (int i = 0; i < listBox1.Items.Count; i++)
                this.saveMap(this.getIdByIndex(i), dir);
        }

        /// <summary>
        /// Save the entire project
        /// </summary>
        private void enregistrerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.saveAllMaps(Directory.GetCurrentDirectory() + "\\GFX\\Maps");
        }

        /// <summary>
        /// Order the files by their names (id).
        /// </summary>
        /// <param name="files">Array of files</param>
        /// <returns>Array of ordered files</returns>
        /// NB Not quite optimal.
        private string[] sortFiles(string[] files)
        {
            string[] orderedFiles = new string[files.Length];
            int[] filesId = new int[files.Length];
            for (int i = 0; i < filesId.Length; i++)
                filesId[i] = this.getIdByFileName(files[i]);

            int minIndexFile = 0;
            for (int y = 0; y < files.Length; y++)
            {
                for (int i = 0; i < files.Length; i++)
                    if (filesId[i] < filesId[minIndexFile])
                        minIndexFile = i;

                orderedFiles[y] = files[minIndexFile];
                filesId[minIndexFile] = 9999;
            }

            return orderedFiles;
        }

        private void openAllMaps(string dir)
        {         
            Methods.setAccessRule(dir);

            try
            {
                string[] files = this.sortFiles(Directory.GetFiles(dir, "*.map.fd"));

                for (int i = 0; i < files.Length; i++)
                {
                    this.createMap(this.getIdByFileName(files[i]), false);
                    this.openMap(files[i]);
                }
            }
            catch (Exception e)
            {
                MessageBox.Show("L'ouverture des cartes a échoué. Message d'erreur : " + e.Message,
                    "Erreur",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);
            }
        }

        /// <summary>
        /// Open the entire project
        /// </summary>
        private void ouvrirToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            this.fetchTilesets();
            comboBox1.SelectedIndex = 0;

            this.openAllMaps(Directory.GetCurrentDirectory() + "\\GFX\\Maps");
            this.restoreMap();

            this.enableGameItems();
        }

        #endregion

        #region Map

        /// <summary>
        /// New map
        /// </summary>
        private void nouvelleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.createMap();
        }

        /// <summary>
        /// Ensures that the save will be ok.
        /// </summary>
        /// <remarks>Call this before saving a map.</remarks>
        private void initMapSave(string dir)
        {
            Directory.CreateDirectory("GFX/Maps");
            Methods.setAccessRule(dir);
        }

        private void saveMap(int mapIndex, string dir)
        {
            FileStream fs = File.Open(dir + "\\" + mapIndex + ".map.fd", FileMode.Create);

            try
            {
                WorldMap.getMap(mapIndex).save(fs);
            }
            catch (SerializationException ex)
            {
                MessageBox.Show("La sauvegarde de la carte a échoué. Message d'erreur : " + ex.Message,
                    "Erreur",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);
                throw;
            }
            finally
            {
                fs.Close();
            }
        }

        /// <summary>
        /// Save the current map.
        /// </summary>
        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string dir = Directory.GetCurrentDirectory() + "\\GFX\\Maps";

            this.initMapSave(dir);
            this.saveMap(this.getIdByIndex(listBox1.SelectedIndex), dir);
        }

        private int getIdByFileName(string path)
        {
            string[] fileName = path.Split('\\');
            string[] id = fileName[fileName.Length - 1].Split('.');

            return Convert.ToInt32(id[0]);
        }

        private bool openMap(string path)
        {
            try
            {
                FileStream fs = File.Open(path, FileMode.Open);

                int id = this.getIdByFileName(path);

                WorldMap.getMap(id).load(fs, true);
                WorldMap.getMap(id).Index = id;

                fs.Close();
            }
            catch (SerializationException ex)
            {
                MessageBox.Show("La désérialisation de la carte a échoué. Message d'erreur : " + ex.Message,
                    "Erreur",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);
                throw;
            }
            catch (FileNotFoundException e)
            {
                MessageBox.Show("La désérialisation de la carte a échoué. Message d'erreur : " + e.Message,
                    "Erreur",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);
                return false;
            }
            catch (IOException e)
            {
                MessageBox.Show("L'ouverture de la carte a échoué. Message d'erreur : " + e.Message,
                    "Erreur",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error,
                    MessageBoxDefaultButton.Button1);
                throw;
            }

            return true;
        }

        //Not quite useful. Maybe later... For now it's only good to test fast.
        /// <summary>
        /// Open a single map.
        /// </summary>
        private void ouvrirToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string dir = Directory.GetCurrentDirectory() + "\\GFX\\Maps";

            this.createMap(false);
            Methods.setAccessRule(dir);

            if (this.openMap(dir + "\\0.map.fd"))
                this.restoreMap();
        }

        private void propriétésToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            FormMapProperties frmMapProperties = new FormMapProperties(WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)));
            frmMapProperties.Show();
        }
        
        #endregion

        #region Layer

        /// <summary>
        /// New layer
        /// </summary>
        private void nouveauToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            this.createLayer();
            this.updateComboBox2();

            this.mapDisplayLayers.Add(new Bitmap(Map.WidthInTile * TileEngine.Tile.Width,
                Map.HeightInTile * TileEngine.Tile.Height));
        }

        #endregion

        #endregion

        #region Toolbar

        /// <summary>
        /// Fill the map with the selected tile or attribute.
        /// </summary>
        private void button1_Click(object sender, EventArgs e)
        {
            if (tabControl1.SelectedIndex == 0 && this.selectedTiles != null)
                this.fillTile();
            else if (tabControl1.SelectedIndex == 1)
                this.fillAttribute();

            this.paintMapDisplay();
        }

        private void updateComboBox2()
        {
            comboBox2.DataSource = null;
            comboBox2.DisplayMember = "IndexName";
            comboBox2.DataSource = WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers;
            comboBox2.SelectedIndex = WorldMap.getMap(this.getIdByIndex(listBox1.SelectedIndex)).Layers.Count - 1;
        }

        #endregion

        #region TabControl

        #region Layers

        private void ComboBox1_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            if (!WorldMap.getTileset(comboBox1.SelectedItem.ToString()).IsInitialized)
                WorldMap.getTileset(comboBox1.SelectedItem.ToString()).initialize(TileEngine.Tile);

            this.initializeTilesetLayers();
            this.fillTilesetReview();
        }

        private void selectMap(int index)
        {
            listBox1.DataSource = null;
            listBox1.DisplayMember = "IndexName";
            listBox1.DataSource = WorldMap.getMaps();

            listBox1.SelectedIndex = index;
        }

        #endregion

        #region Attributes

        private void checkBox1_CheckedChanged(object sender, EventArgs e)
        {
            this.paintMapDisplay();
        }

        #endregion

        #region Entities

        private void checkBox3_CheckedChanged(object sender, EventArgs e)
        {
            this.paintMapDisplay();
        }

        #endregion

        #endregion

        #region Map ListBox

        private void listBox1_MouseClick(object sender, MouseEventArgs e)
        {
            int index = listBox1.IndexFromPoint(e.Location);

            if (index != ListBox.NoMatches)
            {
                this.initializeBmps(false);
                this.restoreMap();
            }
        }

        #endregion
    }
}
